/*  Copyright (c) 2012 William Rogers and James Boud

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Windows.Forms;
using GameTime = Microsoft.Xna.Framework.GameTime;
namespace JBBRXG11
{
    public class Game : IDisposable
    {
        GameWindow window;
        bool close_requested;
        ContentManager content;
        List<IDisposable> usercontent;
        static GraphicsDevice device = null;  // This is static so content manager can find it - bad??

        internal protected GameWindow Window { get { return window; } }
        internal protected ContentManager Content { get { return content; } set { content = Content; } }
        internal protected static GraphicsDevice GraphicsDevice { get { return device; } }
        public void UserContentAdd(IDisposable item) { usercontent.Add(item); }
        //internal List<IDisposable> UserContent { get { return usercontent; } }

        double maxdrawdelayms;   // determines number of updates before a draw recovering from time lag
        bool isfixedtimestep;
        TimeSpan targetelapsedtime;
        DateTime start_time, last_time;
        TimeSpan total_game_time;
        GameTime currentgametime;
        bool suppressdraw;       // lets an update suppress the next draw

        public bool IsFixedTimeStep { get { return isfixedtimestep; } set { isfixedtimestep = value; } }
        public TimeSpan TargetElapsedTime { get { return targetelapsedtime; } set { targetelapsedtime = value; } }

        public Game()
        {
            window = new GameWindow();
            window.Window_Close_Clicked += new EventHandler(window_Close_Clicked);
            //TODO: Reset Mouse to false. Debugging.
            IsMouseVisible = true;
            content = new ContentManager(new DummyServiceProvider(), "");
            usercontent = new List<IDisposable>();

            isfixedtimestep = true;
            targetelapsedtime = new TimeSpan(10000000 / 60);
            maxdrawdelayms = 500;  // msec - maximum delay between draws allowed for repeated update catchup
        }

        protected virtual void Initialize()
        {
            LoadContent();
        }

        protected virtual void LoadContent()
        {
        }

        protected virtual void UnloadContent()
        {
        }

        protected virtual void Update(GameTime gameTime)
        {
        }

        protected virtual bool BeginDraw()
        {
            return true;
        }

        protected virtual void Draw(GameTime gameTime)
        {
        }

        protected virtual void EndDraw()
        {
            GraphicsDevice.EndDraw();
        }

        protected virtual void BeginRun()
        {
        }

        protected virtual void EndRun()
        {
        }

        public void SuppressDraw()
        {
            suppressdraw = true;
        }

        public void Tick()
        {
            // TODO Test the variations on the game time algorithm here
            //      This is a first attempt and has had little testing
            //      It is based on the description in 
            //      http://blogs.msdn.com/b/shawnhar
            //         /archive/2007/07/25/understanding-gametime.aspx
            //      but clearly not the same because it seems more
            //      likely to catch up when behind - I let it iterate 
            //      on update for up to 500ms which can get through
            //      a large number of essentially empty updates.
            //      I suspect that the real algorithm is limited
            //      to thirty update calls per tick.  I don't know
            //      how that it effected by update processing time.
            suppressdraw = false;
            DateTime now = DateTime.Now;
            TimeSpan elapsed = now - last_time;
            if (isfixedtimestep)
            {
                double elapsedms = elapsed.TotalMilliseconds;
                double targetms = targetelapsedtime.TotalMilliseconds;
                if (elapsedms <= targetms)
                {   // On time or too fast
                    int sleeptime = (int)(targetms - elapsedms + 0.5);
                    if (sleeptime > 0)
                    {
                        System.Threading.Thread.Sleep(sleeptime);
                        now = DateTime.Now;
                        elapsed = now - last_time;
                    }
                    total_game_time += targetelapsedtime;
                    last_time = now;
                    //currentgametime = new GameTime(now - start_time, elapsed, total_game_time, targetelapsedtime, false);
                    currentgametime = new GameTime(total_game_time, targetelapsedtime, false);
                    Microsoft.Xna.Framework.FrameworkDispatcher.Update();  // Needed for audio (maybe video) to update
                    Update(currentgametime);
                }
                else
                {   // Too slow - try to catch up with repeated update calls and no drawing
                    double deficit = elapsedms - targetms;
                    double drawdelayms = 0;
                    while (deficit > 0 && drawdelayms < maxdrawdelayms)
                    {
                        total_game_time += targetelapsedtime;
                        last_time = now;
                        currentgametime = new GameTime(total_game_time, targetelapsedtime, true);
                        Microsoft.Xna.Framework.FrameworkDispatcher.Update();  // Needed for audio (maybe video) to update
                        Update(currentgametime);
                        now = DateTime.Now;
                        elapsed = now - last_time;
                        elapsedms = elapsed.TotalMilliseconds;
                        drawdelayms += elapsedms;
                        deficit += elapsedms - targetms;
                    }
                }
            }
            else
            {
                total_game_time += elapsed;
                last_time = now;
                currentgametime = new GameTime(total_game_time, elapsed, false);
                Microsoft.Xna.Framework.FrameworkDispatcher.Update();  // Needed for audio (maybe video) to update
                Update(currentgametime);
            }
            if (!suppressdraw && BeginDraw())
            {
                Draw(currentgametime);
                EndDraw();
            }
        }

        public void ResetElapsedTime()
        {
            start_time = DateTime.Now;
            last_time = start_time;
            total_game_time = TimeSpan.Zero;
        }

        public void Run()
        {
            // Constructor has run and that should have created the
            // graphics device manager and set windowing options
            // Create graphics device and finalize window at this point
            GraphicsDeviceManager dev_manager = GraphicsDeviceManager.GetGameManager(this);
            if (device != null)
                throw new Exception("DXna(NYI): Cannot handle multiple device managers");
            Window.SetClientSize(dev_manager.PreferredBackBufferWidth, dev_manager.PreferredBackBufferHeight);
            device = dev_manager.CreateDevice();  // may update window client size information in case of full screen display
            Initialize();

            ResetElapsedTime();
            //Run window
            close_requested = false;
            window.Show();
            BeginRun();
            while (!close_requested)
            {
                System.Windows.Forms.Application.DoEvents();
                Tick();
            }
            EndRun();
            OnExiting(this, new EventArgs());

            foreach (IDisposable dis in usercontent) dis.Dispose();
            usercontent.Clear();
            device.Dispose();
            dev_manager.Dispose();
            
            //TODO: Check that this is actually disposing the objects.
            //foreach (SlimDX.ComObject objectLeftToDispose in SlimDX.ObjectTable.Objects)
            //{
            //    objectLeftToDispose.Dispose();
            //}
            // Check that we have disposed of all COM objects
            string still_alive = SlimDX.ObjectTable.ReportLeaks();
            if (still_alive != "Total of 0 objects still alive.\n")
                System.Windows.Forms.MessageBox.Show(still_alive);

            Window.Close();
        }

        public void Exit()
        {
            close_requested = true;
        }

        void window_Close_Clicked(object sender, EventArgs e)
        {
            Exit();
        }

        protected virtual void OnExiting(object sender, EventArgs args)
        {
        }

        public bool IsMouseVisible
        {
            get { return Window.MouseVisible; }
            set { Window.MouseVisible = value; }
        }

        public void Dispose() // Dispose unmanaged resources
        {
        }

        protected virtual void Dispose(bool disposing)
        {
            //if (disposing) release all resources else only unmanaged
        }
    }

    class DummyServiceProvider : IServiceProvider
    {
        object IServiceProvider.GetService(Type serviceType)
        {
            throw new NotImplementedException();
        }
    }
}


/*
Things that I have not done yet
 
 * GameComponentCollection does not exist
 * Anything to do with active/inactive is not done
 * Some more event handlers could be wired up

*/

/*

    public class Game : IDisposable
    {
        // Summary:
        //     Gets the collection of GameComponents owned by the game. Reference page contains
        //     links to related code samples.
        public GameComponentCollection Components { get; }
        //
        // Summary:
        //     Gets or sets the time to sleep when the game is inactive.
        public TimeSpan InactiveSleepTime { get; set; }
        //
        // Summary:
        //     Indicates whether the game is currently the active application. Reference
        //     page contains links to related code samples.
        public bool IsActive { get; }
        //
        //
        //
        // Summary:
        //     Gets the GameServiceContainer holding all the service providers attached
        //     to the Game. Reference page contains links to related conceptual articles.
        public GameServiceContainer Services { get; }
        //

        // Summary:
        //     Raised when the game gains focus.
        public event EventHandler Activated;
        //
        // Summary:
        //     Raised when the game loses focus.
        public event EventHandler Deactivated;
        //
        // Summary:
        //     Raised when the game is being disposed.
        public event EventHandler Disposed;
        //
        // Summary:
        //     Raised when the game is exiting.
        public event EventHandler Exiting;


        ////     Obsolete. Called when graphics resources need to be loaded. Override this
        ////     method to load any game-specific graphics resources. Reference page contains
        ////     links to related code samples.
        ////
        //// Parameters:
        ////   loadAllContent:
        ////     true if all graphics resources need to be loaded; false if only manual resources
        ////     need to be loaded.
        //[Obsolete("The LoadGraphicsContent method is obsolete and will be removed in the future.  Use the LoadContent method instead.")]
        //[EditorBrowsable(EditorBrowsableState.Never)]
        //protected virtual void LoadGraphicsContent(bool loadAllContent);
        //
        // Summary:
        //     Raises the Activated event. Override this method to add code to handle when
        //     the game gains focus.
        //
        // Parameters:
        //   sender:
        //     The Game.
        //
        //   args:
        //     Arguments for the Activated event.
        protected virtual void OnActivated(object sender, EventArgs args);
        //
        // Summary:
        //     Raises the Deactivated event. Override this method to add code to handle
        //     when the game loses focus.
        //
        // Parameters:
        //   sender:
        //     The Game.
        //
        //   args:
        //     Arguments for the Deactivated event.
        protected virtual void OnDeactivated(object sender, EventArgs args);
        //

        //
        // Summary:
        //     This is used to display an error message if there is no suitable graphics
        //     device or sound card.
        //
        // Parameters:
        //   exception:
        //     The exception to display.
        protected virtual bool ShowMissingRequirementMessage(Exception exception);
        //
        // Summary:
        //     Obsolete. Called when graphics resources need to be unloaded. Override this
        //     method to unload any game-specific graphics resources.
        //
        // Parameters:
        //   unloadAllContent:
        //     true if all graphics resources need to be unloaded; false if only manual
        //     resources need to be unloaded.
        [Obsolete("The UnloadGraphicsContent method is obsolete and will be removed in the future.  Use the UnloadContent method instead.")]
        [EditorBrowsable(EditorBrowsableState.Never)]
        protected virtual void UnloadGraphicsContent(bool unloadAllContent);
    }
 
 * */
